package project.messagestack.utils;

import java.util.LinkedList;
import java.util.Vector;

import android.content.ContentResolver;
import android.database.CharArrayBuffer;
import android.database.ContentObserver;
import android.database.Cursor;
import android.database.DataSetObserver;
import android.net.Uri;
import android.os.Bundle;

public class UnionCursor implements Cursor {

  static enum ColumnType {
    BLOB,
    DOUBLE,
    FLOAT,
    INT,
    LONG,
    SHORT,
    STRING
  }
  
  private LinkedList<Object[]> mRows;
  private String[] mColumnNames;
  private int mCurrentRowId;
  private Cursor[] mCursors;
  private int mCmpColumnIndex;
  private boolean mOrderAsc;
  private ColumnType[] mColumnsType;
  
  private int nextInSet(Vector<Integer> without) {
    int ret = -1;
    // determine first free element in array
    for (int i = 0; i < mColumnNames.length; i++) {
      if (!without.contains(i)) { 
        ret = i;
        break;
      }
    }
    ColumnType cmpColumnType = mColumnsType[mCmpColumnIndex];
    for (int i = 0; i < mCursors.length; i++) {
      if (without.contains(i)) continue;
      switch (cmpColumnType) {
        case FLOAT:
        case DOUBLE:
          if (mOrderAsc) {
            if (mCursors[i].getDouble(mCmpColumnIndex) < mCursors[ret].getDouble(mCmpColumnIndex)) {
              ret = i;
            }
          } else {
            if (mCursors[i].getDouble(mCmpColumnIndex) > mCursors[ret].getDouble(mCmpColumnIndex)) {
              ret = i;
            } 
          }
          break;
        case INT:
        case LONG:
        case SHORT:
          if (mOrderAsc) {
            if (mCursors[i].getLong(mCmpColumnIndex) < mCursors[ret].getLong(mCmpColumnIndex)) {
              ret = i;
            }
          } else {
            if (mCursors[i].getLong(mCmpColumnIndex) > mCursors[ret].getLong(mCmpColumnIndex)) {
              ret = i;
            } 
          }
          break;
        case STRING:
          if (mOrderAsc) {
            if ( mCursors[i].getString(mCmpColumnIndex).compareTo(mCursors[ret].getString(mCmpColumnIndex)) < 0) {
              ret = i;
            }
          } else {
            if ( mCursors[i].getString(mCmpColumnIndex).compareTo(mCursors[ret].getString(mCmpColumnIndex)) > 0) {
              ret = i;
            }
          }
          break;
        default:
          Logger.logWarn("Handling default field");
      }
    }
    return ret;
  }
  
  private void combineCursors() {
    Vector<Integer> without = new Vector<Integer>();
    boolean r = false;
    for (int i = 0; i < mCursors.length; i++) {
      r |= mCursors[i].moveToFirst();
      if (mCursors[i].getCount() == 0) without.add(i);
    }
    while (r) {
      while (without.size() != mCursors.length) {
        int n = nextInSet(without);
        Object[] a = new Object[mColumnNames.length];
        for (int i = 0; i < mColumnNames.length; i++) {
          switch (mColumnsType[i]) {
            case DOUBLE:
              a[i] = mCursors[n].getDouble(i);
              break;
            case FLOAT:
              a[i] = mCursors[n].getFloat(i);
              break;
            case INT:
              a[i] = mCursors[n].getInt(i);
              break;
            case LONG:
              a[i] = mCursors[n].getLong(i);
              break;
            case SHORT:
              a[i] = mCursors[n].getShort(i);
              break;
            case STRING:
              a[i] = mCursors[n].getString(i);
              break;
          }
        }
        mRows.add(a);
        without.add(n);
      }
      r = false;
      without.clear();
      for (int i = 0; i < mCursors.length; i++) {
        boolean p = mCursors[i].moveToNext();
        r |= p;
        if (!p) without.add(i);
      }
    }
  }
  
  public UnionCursor(Cursor[] cursors,
                     String[] columnNames,
                     ColumnType[] columnTypes,
                     int cmpColumnIndex,
                     boolean asc) {
    mColumnNames = columnNames;
    mColumnsType = columnTypes;
    mCmpColumnIndex = cmpColumnIndex;
    mOrderAsc = asc;
    mCursors = cursors;
    mRows = new LinkedList<Object[]>();
    combineCursors();
  }
  
  public void close() {
    for (Cursor c : mCursors) {
      c.close();
    }
  }

  public void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer) {
    // TODO Auto-generated method stub

  }

  public void deactivate() {
    // TODO Auto-generated method stub

  }

  public byte[] getBlob(int columnIndex) {
    // TODO Auto-generated method stub
    return null;
  }

  public int getColumnCount() {
    return mColumnNames.length;
  }

  public int getColumnIndex(String columnName) {
    for (int i = 0; i < mColumnNames.length; i++) {
      if (columnName.equals(mColumnNames[i])) {
        return i;
      }
    }
    return 0;
  }

  public int getColumnIndexOrThrow(String columnName)
      throws IllegalArgumentException {
    for (int i = 0; i < mColumnNames.length; i++) {
      if (columnName.equals(mColumnNames[i])) {
        return i;
      }
    }
    throw new IllegalArgumentException();
  }

  public String getColumnName(int columnIndex) {
    return mColumnNames[columnIndex];
  }

  public String[] getColumnNames() {
    return mColumnNames;
  }

  public int getCount() {
    return mRows.size();
  }

  public double getDouble(int columnIndex) {
    return ((Double)mRows.get(mCurrentRowId)[columnIndex]).doubleValue();
  }

  public Bundle getExtras() {
    return Bundle.EMPTY;
  }

  public float getFloat(int columnIndex) {
    return ((Float)mRows.get(mCurrentRowId)[columnIndex]).floatValue();
  }

  public int getInt(int columnIndex) {
    return ((Integer) mRows.get(mCurrentRowId)[columnIndex]).intValue();
  }

  public long getLong(int columnIndex) {
    return ((Long) mRows.get(mCurrentRowId)[columnIndex]).longValue();
  }

  public int getPosition() {
    return mCurrentRowId;
  }

  public short getShort(int columnIndex) {
    return ((Short) mRows.get(mCurrentRowId)[columnIndex]).shortValue();
  }

  public String getString(int columnIndex) {
    return mRows.get(mCurrentRowId)[columnIndex].toString();
  }

  public boolean getWantsAllOnMoveCalls() {
    // TODO Auto-generated method stub
    return false;
  }

  public boolean isAfterLast() {
    return mCurrentRowId == mRows.size();
  }

  public boolean isBeforeFirst() {
    return mCurrentRowId == -1;
  }

  public boolean isClosed() {
    boolean ret = true;
    for (Cursor c : mCursors) {
      ret &= c.isClosed();
    }
    return ret;
  }

  public boolean isFirst() {
    return mCurrentRowId == 0;
  }

  public boolean isLast() {
    return mCurrentRowId == mRows.size()-1;
  }

  public boolean isNull(int columnIndex) {
    return mRows.get(mCurrentRowId)[columnIndex] == null;
  }

  public boolean move(int offset) {
    boolean ret = false;
    int wantedPos = mCurrentRowId + offset;
    if (wantedPos < mRows.size() && wantedPos >= 0) {
      mCurrentRowId += offset;
      ret = true;
    }
    return ret;
  }

  public boolean moveToFirst() {
    if (mRows.size() == 0) {
      return false;
    }
    mCurrentRowId = 0;
    return true;
  }

  public boolean moveToLast() {
    if (mRows.size() == 0) {
      return false;
    }
    mCurrentRowId = mRows.size()-1;
    return true;
  }

  public boolean moveToNext() {
    boolean ret;
    if (isAfterLast()) {
      ret = false;
    } else {
      ret = true;
      mCurrentRowId++;
    }
    return ret;
  }

  public boolean moveToPosition(int position) {
    boolean ret;
    if (position < 0 || position >= mRows.size()) {
      ret = false;
    } else {
      mCurrentRowId = position;
      ret = true;
    }
    return ret;
  }

  public boolean moveToPrevious() {
    boolean ret;
    if (isBeforeFirst()) {
      ret = false;
    } else {
      ret = true;
      mCurrentRowId--;
    }
    return ret;
  }

  public void registerContentObserver(ContentObserver observer) {
    // TODO Auto-generated method stub

  }

  public void registerDataSetObserver(DataSetObserver observer) {
    // TODO Auto-generated method stub

  }

  public boolean requery() {
    boolean ret = false;
    for (Cursor c : mCursors) {
      ret |= c.requery();
    }
    if (ret) {
      mRows.clear();
      combineCursors();
    }
    return ret;
  }

  public Bundle respond(Bundle extras) {
    // TODO Auto-generated method stub
    return null;
  }

  public void setNotificationUri(ContentResolver cr, Uri uri) {
    // TODO Auto-generated method stub

  }

  public void unregisterContentObserver(ContentObserver observer) {
    // TODO Auto-generated method stub

  }

  public void unregisterDataSetObserver(DataSetObserver observer) {
    // TODO Auto-generated method stub

  }

}
